﻿﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
using System.Reflection;
using PServiceBus.Core.Runtime.Extensions;
using System.Collections.Concurrent;
using PServiceBus.Core.Runtime.Messages;
using System.Text;

namespace PServiceBus.Core.Runtime {
    public class ExpressionGenerator {
        internal static readonly Regex _keyRegex = new Regex(@"(\s*(?<Key>[^$3]+)\b\s*(?<Oper>=|!=|<=|>=|>|<|like|regex)\b\s*(?<Value>.+))|(\s*(?<Key>[^$3]+)\b\s*(?<Oper>=|!=|<=|>=|>|<|like|regex)\s*(?<Value>.+))",
            RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase);

        static readonly Regex _containRegex = new Regex(@"(.+)(.Contains\()("".+"")(\))(.+)",
            RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase);

        static readonly ConcurrentDictionary<string, Func<Expression, Expression, Expression>> _cachedExpression =
            new ConcurrentDictionary<string, Func<Expression, Expression, Expression>>();

        static readonly ConcurrentDictionary<string, Delegate> _cachedCompiledExpression =
            new ConcurrentDictionary<string, Delegate>();

        static readonly MethodInfo _toCorrectType = typeof(ExpressionGenerator).GetMethod("ToCorrectType",
                BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
        static readonly MethodInfo _getComplexExpressionValue = typeof(ExpressionGenerator).GetMethod("GetComplexExpressionValue",
                BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
        static readonly MethodInfo _isExpressionValueBase64 = typeof(ExpressionGenerator).GetMethod("IsExpressionValueBase64",
                BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);

        static readonly ConcurrentDictionary<string, FilterDelegate> _cachedFuncs =
            new ConcurrentDictionary<string, FilterDelegate>();

        public delegate bool FilterDelegate(params object[] args);

        static Func<Expression, Expression, Expression> GetOperatorExpression(string oper) {
            oper = oper.ToLower();
            return _cachedExpression.ContainsKey(oper) ? _cachedExpression[oper] :
                new Func<Expression, Expression, Expression>((ex1, ex2) => Expression.Empty());
        }

        static ExpressionGenerator() {
            _cachedExpression["="] = new Func<Expression, Expression, Expression>((ex1, ex2) => Expression.Equal(ex1, ex2));
            _cachedExpression["!="] = new Func<Expression, Expression, Expression>((ex1, ex2) => Expression.NotEqual(ex1, ex2));
            _cachedExpression["<"] = new Func<Expression, Expression, Expression>((ex1, ex2) => Expression.LessThan(ex1, ex2));
            _cachedExpression["<="] = new Func<Expression, Expression, Expression>((ex1, ex2) => Expression.LessThanOrEqual(ex1, ex2));
            _cachedExpression[">"] = new Func<Expression, Expression, Expression>((ex1, ex2) => Expression.GreaterThan(ex1, ex2));
            _cachedExpression[">="] = new Func<Expression, Expression, Expression>((ex1, ex2) => Expression.GreaterThanOrEqual(ex1, ex2));
            _cachedExpression["like"] = new Func<Expression, Expression, Expression>((ex1, ex2) =>
            {
                return Expression.Call(ex1, typeof(string).GetMethod("Contains", new[] { typeof(string) }), ex2);
            });
            _cachedExpression["regex"] = new Func<Expression, Expression, Expression>((ex1, ex2) =>{
                return Expression.Call(typeof(Regex).GetMethod("IsMatch", new []{ typeof(string), typeof(string), typeof(RegexOptions)}),
                    ex1, ex2, Expression.Constant(RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline));
            });
        }

        private Dictionary<string, ParameterExpression> _parameters;
        private string _expression;
        private int _index;
        private List<object> _list = new List<object>();
        private bool _caseSensitive;

        public ExpressionGenerator(string expression, bool caseSensitive = true) {
            Reset(expression, caseSensitive);
        }

        private Expression Parse(List<object> list) {
            var body = default(Expression);
            for (var i = 0; i < list.Count; i++) {
                var operand = list[i] as string;
                if (operand == null) {
                    var result = Parse(list[i]);
                    if (body == null) body = result;
                    else continue;
                } else {
                    if (operand == "||") body = Expression.Or(body, Parse(list[i + 1]));
                    else body = Expression.And(body, Parse(list[i + 1]));
                    i += 1;
                }
            }
            return body;
        }

        private Expression Parse(object obj) {
            return obj is Expression ? obj as Expression : Parse(obj as List<object>);
        }

        public void Reset(string expression, bool caseSensitive = true) {
            _expression = expression;
            _caseSensitive = caseSensitive;
            if (_parameters == null)
                _parameters = new Dictionary<string, ParameterExpression>();
            else _parameters.Clear();
            _index = 0;
        }

        private ParameterExpression GetParameter(string key, ref Type type) {
            var useProp = key.Contains(".");
            key = useProp ? key.Split('.')[0] : key;
            type = useProp ? typeof(string) : type;
            if (_parameters.ContainsKey(key)) return _parameters[key];
            return _parameters[key] = Expression.Parameter(type, key);
        }

        public Delegate BuildDelegate() {
            if (String.IsNullOrWhiteSpace(_expression)) return null;
            return _cachedCompiledExpression.GetOrAdd(_expression, exp =>
            {
                Process(_expression, ref _index, _list);
                var parameters = _parameters.OrderBy(x => x.Key).Select(x => x.Value).ToList();
                var lambda = Expression.Lambda(Parse(_list), parameters);
                return lambda.Compile();
            });
        }

        public FilterDelegate Build() {
            if (String.IsNullOrWhiteSpace(_expression)) return null;
            return _cachedFuncs.GetOrAdd(_expression, key =>
            {
                var del = BuildDelegate();
                var getInvokeMethod = typeof(Delegate).GetMethod("GetInvokeMethod",
                     BindingFlags.NonPublic | BindingFlags.Instance);
                var runTypeHandle = Type.GetType("System.RuntimeMethodHandleInternal");
                var runType = Type.GetType("System.RuntimeType");
                var runTypeMethodBase = runType.GetMethod("GetMethodBase",
                     BindingFlags.NonPublic | BindingFlags.Static,
                     null, new[] { runType, runTypeHandle }, null);
                var invokeHandle = (IntPtr)getInvokeMethod.Invoke(del, null);
                var runHandleCtor = runTypeHandle.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance,
                    null, new[] { typeof(IntPtr) }, null);
                var runHandle = runHandleCtor.Invoke(new object[] { invokeHandle });
                var runMethod = runTypeMethodBase.Invoke(null,
                    new[] { del.GetType(), runHandle }) as MethodInfo;

                return args =>
                {
                    return (bool)runMethod.Invoke(del, args);
                };
            });
        }

        public static FilterDelegate Build(string expression, bool caseSensitive = true) {
            return new ExpressionGenerator(expression, caseSensitive: caseSensitive).Build();
        }

        public static T ToCorrectType<T>(string value) {
            return value.ToExpectedType().CastTo<T>();
        }

        public static bool IsExpressionValueBase64(string base64xml) {
            return base64xml.IsXmlBase64();
        }

        public static string GetComplexExpressionValue(string base64xml, string key) {
            var isBase64Xml = base64xml.IsXmlBase64();
            if (!isBase64Xml) return base64xml;
            var xml = String.Format("<List><Items type=\"Items\"><ExpressionGeneratorComplexMessage>{0}</ExpressionGeneratorComplexMessage></Items></List>", base64xml.XmlBase64ToXml());
            var messageMapper = MessageMapper.Map(xml);
            var messageMapperToken = messageMapper.Tokens.FirstOrDefault();
            var value = messageMapperToken[key];
            if (String.IsNullOrWhiteSpace(value)) {
                key = String.Join(".", key.Split('.').Skip(1));
                value = messageMapperToken[key];
            }
            return value;
        }

        private static Tuple<string, string> ReadToOperand(string expression, ref int index) {
            var sb = new StringBuilder();
            bool isOr = false, isAnd = false;
            var length = expression.Length;
            while (index < length) {
                sb.Append(expression[index++]);
                var hasOp = sb.Length >= 2 &&
                    ((isAnd = (sb[sb.Length - 2] == '&' && sb[sb.Length - 1] == '&')) ||
                    (isOr = (sb[sb.Length - 2] == '|' && sb[sb.Length - 1] == '|')));
                if (hasOp) break;
            }
            return new Tuple<string, string>((isOr || isAnd) ? sb.ToString().Substring(0, sb.Length - 2) : sb.ToString(),
                isOr ? "||" : isAnd ? "&&" : null);
        }

        private static string ReadToBrace(string expression, ref int index, string startExp, string delimeter = "") {
            var count = Math.Max(startExp.Count(x => x == '(') - startExp.Count(x => x == ')'), 0);
            var chr = '\0';
            var sb = new StringBuilder(startExp + delimeter);
            var length = expression.Length;
            while (count > 0 && index < length) {
                sb.Append(chr = expression[index++]);
                if (chr == '(') count++;
                else if (chr == ')') count--;
            }
            return sb.ToString().Trim();
        }

        private static Regex _matchCharacter = new Regex(@"[a-zA-Z0-9_$@%^*-+!~`=/><,\[\]\.\?""]", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Compiled);

        private void Process(string expression, ref int index, List<object> list) {
            if (index >= expression.Length) return;
            var tuple = ReadToOperand(expression, ref index);
            var exp = tuple.Item1;
            var operand = tuple.Item2;
            var hasBraces = exp.Contains("(");
            var inStringQuote = false;
            if (hasBraces) {
                var indexOfBrace = exp.IndexOf('(') - 1;
                if (indexOfBrace >= 0) {
                    var @char = expression[indexOfBrace].ToString();
                    inStringQuote = _matchCharacter.IsMatch(@char);
                }
            }
            if (hasBraces && !inStringQuote) {
                var items = new List<object>();
                var create = false;
                list.Add(items);
                var expr = ReadToBrace(expression, ref index, exp, operand);
                var idx = 0;
                if (expr.StartsWith("(") && expr.EndsWith(")"))
                    expr = expr.Substring(1, expr.Length - 2);
                else if (expr.StartsWith("(") && (expr.EndsWith("||") || expr.EndsWith("&&"))) {
                    exp = exp.Trim();
                    Create(exp.Substring(1, exp.Length - 2), operand, list);
                    create = true;
                }
                if (!create) {
                    Process(expr, ref idx, items);
                    tuple = ReadToOperand(expression, ref index);
                    if (!String.IsNullOrWhiteSpace(tuple.Item2)) list.Add(tuple.Item2);
                }
            } else if (!String.IsNullOrWhiteSpace(exp)) {
                Create(exp, operand, list);
            }
            Process(expression, ref index, list);
        }


        private void Create(string expression, string operand, List<object> list) {
            expression = expression.Trim();
            var isString = false;
            var match = _keyRegex.Match(expression);
            var key = match.Groups["Key"].Value.Trim();
            var value = match.Groups["Value"].Value.Trim();
            if (!_caseSensitive) value = value.ToLower();
            if ((isString = value.StartsWith("\"")) && value.EndsWith("\""))
                value = value.Substring(1, value.Length - 2);
            object obj = isString ? value : value.ToExpectedType();
            var oper = match.Groups["Oper"].Value;
            var type = obj.GetType();
            var objType = obj.GetType();
            var keyConstant = Expression.Constant(key, typeof(string));
            var param = GetParameter(key, ref type);
            var paramResult = key.Contains(".") ?
            Expression.Call(_toCorrectType.MakeGenericMethod(objType), Expression.Call(_getComplexExpressionValue,
            Expression.Call(param, type.GetMethod("ToString", Type.EmptyTypes)), keyConstant)) :
            param as Expression;
            var result = GetOperatorExpression(oper)(paramResult, Expression.Constant(obj));
            list.Add(result);
            if (operand != null) list.Add(operand);
        }

        private static object Normalize(object value) {
            if (typeof(bool) == value.GetType()) return value.ToString().ToLower();
            return value;
        }

        private static string GetOperator(ExpressionType type) {
            return type == ExpressionType.And || type == ExpressionType.AndAlso ? " && " :
                type == ExpressionType.Or || type == ExpressionType.OrElse ? " || " :
                type == ExpressionType.GreaterThan ? " > " :
                type == ExpressionType.GreaterThanOrEqual ? " >= " :
                type == ExpressionType.LessThan ? " < " :
                type == ExpressionType.LessThanOrEqual ? " <= " :
                type == ExpressionType.Equal ? " = " :
                type == ExpressionType.NotEqual ? " != " :
                type == ExpressionType.Call ? "like" :
                string.Empty;
        }

        private static void WalkExpression(Expression expression, StringBuilder builder) {
            var binary = expression as BinaryExpression;
            if (binary != null) {
                builder.Append("(");
                WalkExpression(binary.Left, builder);
                builder.Append(GetOperator(binary.NodeType));
                WalkExpression(binary.Right, builder);
                builder.Append(")");
            } else {
                var field = expression as MemberExpression;
                var methodCall = expression as MethodCallExpression;
                if (!(field == null && methodCall == null)) {
                    if (expression.ToString().Contains("_DisplayClass"))
                        builder.Append(Normalize(Expression.Lambda(expression).Compile().DynamicInvoke()));
                    else builder.Append(expression.ToString());
                } else {
                    var constant = expression as ConstantExpression;
                    if (constant != null) builder.Append(Normalize(constant.Value));
                    else {
                        var parameter = expression as ParameterExpression;
                        if (parameter != null) builder.Append(parameter.Name);
                    }
                }
            }
        }

        public static string ExpressionToFilter<TMessage>(Expression<Func<TMessage, bool>> expression) where TMessage : class {
            var sb = new StringBuilder();
            var param = expression.Parameters[0].Name;
            WalkExpression(expression.Body, sb);
            var result = sb.ToString().Replace(param + ".", string.Empty);
            while (result.Contains("Contains"))
                result = _containRegex.Replace(result, "$1 like $3$4$5");
            return result;
        }
    }
}